{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 502 GPU\n",
    "\n",
    "View more, visit my tutorial page: https://morvanzhou.github.io/tutorials/\n",
    "My Youtube Channel: https://www.youtube.com/user/MorvanZhou\n",
    "\n",
    "Dependencies:\n",
    "* torch: 1.0.0\n",
    "* torchvision\n",
    "\n",
    "https://ptorch.com/docs/4/pytorch-video-GPU"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用`.cuda`函数可以将张量移动到GPU上，从而享受GPU带来的加速运算。  \n",
    "\n",
    "所有在CPU上的张量，除了字符张量，都支持在numpy之间转换。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "import torch.nn as nn\n",
    "from torch.autograd import Variable\n",
    "import torch.utils.data as Data\n",
    "import torchvision\n",
    "\n",
    "torch.manual_seed(1)\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "1\n",
      "GeForce RTX 2080 Ti\n",
      "0\n"
     ]
    }
   ],
   "source": [
    "# cuda是否可用\n",
    "print(torch.cuda.is_available())\n",
    "\n",
    "# 返回gpu数量\n",
    "print(torch.cuda.device_count())\n",
    "\n",
    "# 返回gpu名字，设备索引默认从0开始\n",
    "print(torch.cuda.get_device_name(0))\n",
    "\n",
    "# 返回当前设备索引\n",
    "print(torch.cuda.current_device())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# run this cell only if CUDA is available\n",
    "if torch.cuda.is_available():\n",
    "    x = x.cuda()\n",
    "    y = y.cuda()\n",
    "    print(x + y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1.2126, 0.8518, 0.9011],\n",
      "        [1.6718, 0.6849, 1.1137],\n",
      "        [0.5952, 1.1706, 0.8538],\n",
      "        [0.7786, 1.1626, 1.3227],\n",
      "        [1.4544, 0.3164, 1.2735]])\n"
     ]
    }
   ],
   "source": [
    "x = torch.rand(5, 3)\n",
    "y = torch.rand(5, 3)\n",
    "\n",
    "# 在不支持CUDA的机器下，下一步还是在CPU上运行\n",
    "device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n",
    "# Tensors can be moved onto any device using the .to method.\n",
    "x = x.to(device)\n",
    "y = y.to(device)\n",
    "print(x+y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# run this cell only if CUDA is available\n",
    "# use ``torch.device`` objects to move tensors in and out of GPU\n",
    "if torch.cuda.is_available():\n",
    "    device = torch.device(\"cuda\")          # a CUDA device object\n",
    "    y = torch.ones_like(x, device=device)  # directly create a tensor on GPU\n",
    "    x = x.to(device)                       # or just use strings ``.to(\"cuda\")``\n",
    "    z = x + y\n",
    "    print(z)\n",
    "    print(z.to(\"cpu\", torch.double))       # ``.to`` can also change dtype together!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "此外，还可以使用`tensor.cuda()` 的方式将tensor拷贝到gpu上，但是这种方式不太推荐。\n",
    "\n",
    "此处可能发现GPU运算的速度并未提升太多，这是因为x和y太小且运算也较为简单，而且将数据从内存转移到显存还需要花费额外的开销。GPU的优势需在大规模数据和复杂运算下才能体现出来。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 识别手写数字"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "EPOCH = 1\n",
    "BATCH_SIZE = 50\n",
    "LR = 0.001\n",
    "DOWNLOAD_MNIST = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_data = torchvision.datasets.MNIST(\n",
    "    root='./mnist/', \n",
    "    train=True, \n",
    "    transform=torchvision.transforms.ToTensor(), \n",
    "    download=DOWNLOAD_MNIST,)\n",
    "\n",
    "train_loader = Data.DataLoader(\n",
    "    dataset=train_data, \n",
    "    batch_size=BATCH_SIZE, \n",
    "    shuffle=True)\n",
    "\n",
    "test_data = torchvision.datasets.MNIST(\n",
    "    root='./mnist/', train=False)\n",
    "\n",
    "# !!!!!!!! Change in here !!!!!!!!! #\n",
    "test_x = Variable(torch.unsqueeze(test_data.test_data, dim=1)).type(torch.FloatTensor)[:2000].cuda()/255. # Tensor on GPU\n",
    "test_y = test_data.test_labels[:2000].cuda()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CNN(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(CNN, self).__init__()\n",
    "        self.conv1 = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=1, out_channels=16, kernel_size=5, stride=1, padding=2,),                      \n",
    "            nn.ReLU(), \n",
    "            nn.MaxPool2d(kernel_size=2),)\n",
    "        self.conv2 = nn.Sequential(\n",
    "            nn.Conv2d(16, 32, 5, 1, 2), \n",
    "            nn.ReLU(), \n",
    "            nn.MaxPool2d(2),)\n",
    "        self.out = nn.Linear(32 * 7 * 7, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.conv1(x)\n",
    "        x = self.conv2(x)\n",
    "        x = x.view(x.size(0), -1)\n",
    "        output = self.out(x)\n",
    "        return output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "CNN(\n",
       "  (conv1): Sequential(\n",
       "    (0): Conv2d(1, 16, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))\n",
       "    (1): ReLU()\n",
       "    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  )\n",
       "  (conv2): Sequential(\n",
       "    (0): Conv2d(16, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))\n",
       "    (1): ReLU()\n",
       "    (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)\n",
       "  )\n",
       "  (out): Linear(in_features=1568, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cnn = CNN()\n",
    "\n",
    "# !!!!!!!! Change in here !!!!!!!!! #\n",
    "cnn.cuda()      # Moves all model parameters and buffers to the GPU."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "optimizer = torch.optim.Adam(cnn.parameters(), lr=LR)\n",
    "loss_func = nn.CrossEntropyLoss()\n",
    "\n",
    "losses_his = []"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch:  0 | train loss: 2.2759 | test accuracy: 0.15\n",
      "Epoch:  0 | train loss: 0.5997 | test accuracy: 0.83\n",
      "Epoch:  0 | train loss: 0.2845 | test accuracy: 0.89\n",
      "Epoch:  0 | train loss: 0.1593 | test accuracy: 0.90\n",
      "Epoch:  0 | train loss: 0.1017 | test accuracy: 0.93\n",
      "Epoch:  0 | train loss: 0.1076 | test accuracy: 0.94\n",
      "Epoch:  0 | train loss: 0.2076 | test accuracy: 0.95\n",
      "Epoch:  0 | train loss: 0.1616 | test accuracy: 0.95\n",
      "Epoch:  0 | train loss: 0.1724 | test accuracy: 0.96\n",
      "Epoch:  0 | train loss: 0.2212 | test accuracy: 0.96\n",
      "Epoch:  0 | train loss: 0.0955 | test accuracy: 0.97\n",
      "Epoch:  0 | train loss: 0.0778 | test accuracy: 0.97\n",
      "Epoch:  0 | train loss: 0.0213 | test accuracy: 0.96\n",
      "Epoch:  0 | train loss: 0.1520 | test accuracy: 0.96\n",
      "Epoch:  0 | train loss: 0.0652 | test accuracy: 0.97\n",
      "Epoch:  0 | train loss: 0.0848 | test accuracy: 0.97\n",
      "Epoch:  0 | train loss: 0.1026 | test accuracy: 0.97\n",
      "Epoch:  0 | train loss: 0.0436 | test accuracy: 0.98\n",
      "Epoch:  0 | train loss: 0.0602 | test accuracy: 0.97\n",
      "Epoch:  0 | train loss: 0.0331 | test accuracy: 0.98\n",
      "Epoch:  0 | train loss: 0.0301 | test accuracy: 0.98\n",
      "Epoch:  0 | train loss: 0.0392 | test accuracy: 0.98\n",
      "Epoch:  0 | train loss: 0.1148 | test accuracy: 0.98\n",
      "Epoch:  0 | train loss: 0.0118 | test accuracy: 0.98\n"
     ]
    }
   ],
   "source": [
    "for epoch in range(EPOCH):\n",
    "    for step, (x, y) in enumerate(train_loader):\n",
    "\n",
    "        # !!!!!!!! Change in here !!!!!!!!! #\n",
    "        b_x = Variable(x).cuda()    # Tensor on GPU\n",
    "        b_y = Variable(y).cuda()    # Tensor on GPU\n",
    "\n",
    "        output = cnn(b_x)\n",
    "        loss = loss_func(output, b_y)\n",
    "        losses_his.append(loss.data.item())\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "\n",
    "        if step % 50 == 0:\n",
    "            test_output = cnn(test_x)\n",
    "\n",
    "            # !!!!!!!! Change in here !!!!!!!!! #\n",
    "            pred_y = torch.max(test_output, 1)[1].cuda().data.squeeze()  # move the computation in GPU\n",
    "\n",
    "            accuracy = torch.sum(pred_y == test_y).type(torch.FloatTensor) / test_y.size(0)\n",
    "            print('Epoch: ', epoch, '| train loss: %.4f' % loss.data.item(), '| test accuracy: %.2f' % accuracy)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.plot(losses_his, label='loss')\n",
    "plt.legend(loc='best')\n",
    "plt.xlabel('Steps')\n",
    "plt.ylabel('Loss')\n",
    "plt.ylim((0, 1))\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([7, 2, 1, 0, 4, 1, 4, 9, 5, 9], device='cuda:0') prediction number\n",
      "tensor([7, 2, 1, 0, 4, 1, 4, 9, 5, 9], device='cuda:0') real number\n"
     ]
    }
   ],
   "source": [
    "# !!!!!!!! Change in here !!!!!!!!! #\n",
    "test_output = cnn(test_x[:10])\n",
    "pred_y = torch.max(test_output, 1)[1].cuda().data.squeeze() # move the computation in GPU\n",
    "\n",
    "# 使数据转换为cpu格式，才能使用numpy、matplotlib等库进行处理\n",
    "# pred_y = pred_y.cpu()\n",
    "\n",
    "print(pred_y, 'prediction number')\n",
    "print(test_y[:10], 'real number')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
